本篇文章將會涵蓋以下重點
在講解useState之前,我們先撰寫以下的範例
import React, { useState } from 'react'
const App = () => {
let number = 123;
const incrementHandler = () => {
number +=1;
console.log(number);
};
return (
<div>
<div>{number}</div>
<button onClick={incrementHandler}>+</button>
</div>
);
}
export default App
當我們點擊onClick的時候,可以打開主控台查看,資料的確有變動,但是畫面卻沒有更動
如下圖
原因是我們並沒有告知react number數字已經更新了
,必須使用state來讓react知道畫面必須更新
state是react所設置很特別的變數,透過該變數與瀏覽器的畫面綁定,當state改變的同時就改變瀏覽器的畫面
。
由於渲染畫面是非同步的行為,前端很常做的事情是
事件觸發→資料改變→操縱DOM→瀏覽器更新畫面(非同步行為)
但是當事件多的時候可能就會變成下圖
react框架為了統一管理這些事件處理,所以用state這個特別的變數,所以整個流程如下
參考資料
Jquery Vs React js. Reactjs is a smart and promising framework
在偵測到state改變到畫面更新過程大概如下圖
流程大致如下
由於實際操作Browser Dom是非常消耗效能的一件事情,因此透過Virtual Dom將比對子元件後,當前次的子元件與要改變的子元件不同的時候,將其子元件底下連接的所有元件直接移除後替換成改變後的子元件。
多虧Virtual Dom與diff演算法的作法,使我們更有效的管理畫面與資料變動的依賴關係,順帶一提像是知名前端框架—vue的作法也是使用Virtual DOM的方式。
如下圖
React Virtual DOM Explained in Plain English
Rendering Mechanism
因此為了讓資料可以正確被渲染,這時候我們就要使用react其中一個hook叫做useState
。
根據官方基礎的 Hook的文檔
以下節錄自官方文檔
useState將會回傳一個state 的值,以及更新 state 的 function。
在首次 render時,回傳的state的值會跟第一個參數(initialState)一樣。
setState function 是用來更新 state。它接收一個新的 state 並將 component的重新 render 排進佇列。
在後續的重新 render,useState 回傳的第一個值必定會是最後更新的 state。
等等將會解說這些文字是什麼
首先可以藉由一些方式理解useState是什麼,嘗試著console.log(useState(123))
的時候可以發現,這個function 執行結果它會回傳一個陣列。
import React from 'react'
const App = () => {
console.log(useState(123));
return (
<div>App</div>
)
}
export default App
該陣列索引值0表示的是變數的初始值,索引值1表示是之後要用來對這個state設值的funciton,慣例上我們都會將其設定為set"你的變數名字"
的函式。
通常我們會使用ES6解構賦值的方式來宣告state
因此改寫如下
//以上省略
const App = () => {
const [number,setNumber] = useState(123);
return (
<div>App</div>
)
}
//以下省略
如果對於解構賦值不太理解的也可以參考官方文檔方括號代表什麼或是之前撰寫解構賦值Destructuring assignment—為什麼需要和用途的文章
這時候123就會設定成state的初始值,setNumber是用來改變number值的函式。
還記得原先寫的函式嗎?
這時候我們就可以改寫成如下即可讓react知道畫面需要更新
//import部分以上省略
const [number, setNumber] = useState(123);
const incrementHandler = () => {
setNumber(number + 1);
};
//render部分以下省略
setState使用上需要注意什麼呢?以下透過setTimeout範例來探討情形並且簡單實作如果在物件中使用的方式。
需要注意的是地方是setStatus是一個非同步的函式
我們試著改寫範例如下讓其非同步的狀況更加明顯的時候
//import部分以上省略
const [number, setNumber] = useState(123);
const incrementHandler = () => {
setTimeout(function delay() {
setNumber(number + 1);
}, 1000);
};
//render部分以下省略
這時候模擬非同步行為,當我們在一秒鐘內點三下的時候,最後畫面只有更新一次,這是由於畫面函式(也就是整個App component)重新渲染前(也就是setNumber設值後告訴react渲染畫面),number值是都同一個
,即便我們點了三下,畫面函式還沒被重新執行的時候都是指的是同一個,因此最後只更新一次。
根據上一個範例如果想要讓每次按下的同時都可以正確改到值的話,可以使用update函式,我們將其改寫如下
const incrementHandler = () => {
setTimeout(function delay() {
setNumber((prev)=>(prev + 1));
}, 1000);
};
更新函式,其第一個參數值(也就是prev)會等於上次的狀態值,因此在一秒內按下三次的時候就能夠正確累加三次了。
簡言之,在setState裡面帶入的函式是一個特殊函式被稱為更新函式
,可以確保你所依賴的state有正確被更新。
其他類似的範例像是我們想要在同一個按下事件執行三次setState函式的話,一樣可以使用update函式,下面範例當我們按下+按鈕的時候就能對數字+3了。
這邊就可以發現,如果當你更新後的值是仰賴於更新前值的時候,建議使用畫面更新函式以避免出錯
const [number, setNumber] = useState(123);
const incrementHandler = () => {
setNumber((prev) => (prev + 1));
setNumber((prev) => (prev + 1));
setNumber((prev) => (prev + 1));
};
由於我們要更新status變數的話都要使用setStatus,在物件當中若要更改值是依賴上次的值的時候,通常會使用使用展開運算子進行
範例如下
import React, { useState } from 'react'
const App = () => {
const [number, setNumber] = useState([1, 2, 3]);
const incrementHandler = () => {
setNumber((prev) => (
[...prev, prev[prev.length - 1] + 1]
))
};
return (
<div>
<div>{number[number.length - 1]}</div>
<button onClick={incrementHandler}>+</button>
</div>
);
}
export default App
根據官方文檔的解說
initialState 參數只會在初始 render 時使用,在後續 render 時會被忽略。如果初始 state 需要通過複雜的計算來獲得,你可以傳入一個 function,該 function 只會在初始 render 時被調用
假設我們給予useState的初始值是透過函式計算而來的方式,而且是傳入的初始值是函式執行完
的話,當我們點擊+的時候,觸發setStatue重新渲染畫面的時候(也就是執行整個AppFunction的時候),checkExecuted函式都會被執行
。
但是這個範例當中初始值都是相同的,不應當再次被執行,因此我們透過下個範例來解決這項問題。
觀看以下程式碼
import React, { useState } from 'react'
const App = () => {
function checkExecuted() {
console.log("被執行了");
return 0;
}
const [string, setString] = useState(checkExecuted());
const incrementHandler = () => {
setString((prev) => (prev + 1));
};
return (
<div>
<div>{string}</div>
<button onClick={incrementHandler}>+</button>
</div>
);
}
export default App
可以使用lazy initialzer
的技巧將初始值的計算函式傳入,而不是傳入函式執行的結果
,其回傳值就是useState的初始值。
import React, { useState } from 'react'
const App = () => {
const [string, setString] = useState(() => {
console.log("被執行了");
return 0;
});
const incrementHandler = () => {
setString((prev) => (prev + 1));
};
return (
<div>
<div>{string}</div>
<button onClick={incrementHandler}>+</button>
</div>
);
}
export default App
畫面如下
由於useState是一種hook這邊將官方文件的hook提及注意事項如下
//這裡就是class component
class App extends React.component {
render () {}
}
function App(){
if(this == something){
useState()
}
}